000.Binder 专题导学 —— 如何深入掌握 Binder

7/6/2023

Hello,Hello 大家好,这里是“写给应用开发的 Android Framework 教程”,我是阿豪,今天我们学习的内容是 “Binder 专题导学——如何深入掌握 Binder”。

首先我们看到的是学穿 Binder 篇专题的大纲:

我会把这个思维导图放到课程资料中,我们可以把这个思维导图作为一个向导,来指引我们 Binder 的学习。

# 为什么要学习和掌握 Binder

在学习之前我们要明白我们为什么要学习和掌握 Binder?主要有以下几点原因:

  • Binder 是整个 Android 的基石,所有的系统服务都是基于 Binder,Android 四大组件的底层实现离不开 Binder。如果你要成为 Android 领域的资深研发人员,Binder 是必须要深入掌握的知识之一。
  • 系统开发领域,自定义 Native 和 Java 系统服务是日常工作之一,需要我们熟悉 Android 中与 Binder 相关的类库以及相关的辅助工具。
  • 对于应用开发,ANR 冻屏 卡顿 卡死等偶现 BUG 很可能与 Binder 调用相关,解决这些 bug,需要我们深入掌握理解 Binder 的内部原理。

# 学习 Binder 的预备知识

Binder 是一个 RPC(Remote Procedure Call) 框架,涉及的技术点横跨了 内核 Native JNI Java 四层,学习 Binder 需要较为广泛的知识面,针对大部分 Android 应用开发的知识体系,我总结了以下几点必须要掌握的预备知识:

  • 首先,我们需要入门 Linux 驱动开发,Binder 是一个字符驱动,了解驱动的基本开发流程是阅读 Binder 驱动源码的前提条件
  • 其次,我们需要了解 Linux 内核中常用数据结构的基本使用,Binder 驱动中涉及了很多内核中的数据结构,我们需要了解如何使用这些数据结构
  • 接着,我们需要学习虚拟内存,这是掌握 Binder 工作原理的理论基础
  • 然后,我们需要学习 Linux 文件访问接口,学习了这部分知识才能知道 Binder 驱动是怎么被访问的
  • 最后,我们需要学习 JNI 编程,因为 Binder 的 Java 层中有大量的 JNI 函数

# 了解 Binder 基本原理

有了预备知识的铺垫,我们就可以开始学习 Binder 的基本理论了,主要从 IPC(跨进程数据传输) 和 RPC(远程过程调用) 两个角度来理解 Binder 的工作原理。这部分内容我们会在后续课程中详细分析。

# C 层

在了解了 Binder 基本原理后,我们就可以开始写应用了。

写应用之前,我们要明白,Binder 是一个字符驱动,linux 系统提供了 open ioctl mmap close 等系统调用来使用 Binder 驱动,这些函数是应用层最底层的操作了。

AOSP 源码中有一个 binder.c 源文件,对 open ioctl mmap close 等函数做了封装以适应和简化 Binder 应用层程序的编写。源码中有一个 binder 的测试程序 bctest.c 以及系统服务管家 servicemanager(Android10 及以前) 都是基于 binder.c 的封装实现的。

相比 libbinder 库 C++ 的封装,binder.c 会简单不少 ,方便初学者理解 binder 应用层工作流程。

我们可以模仿 bctest.c service_manager.c 写一个完整的 Binder 应用层 demo。

这个工作已经有大佬完成了:

https://github.com/weidongshan/APP_0003_Binder_C_App

但是也有一些问题,这个代码是基于 Android5 的,稍微有点老了,我在以上实现的基础上做了一些修改和适配工作,使得代码可以在 Android10 上跑起来:

https://github.com/yuandaimaahao/AndroidFrameworkTutorial/tree/main/3.%E5%AD%A6%E7%A9%BFBinder%E7%AF%87/%E6%BA%90%E7%A0%81/BinderCDemo

通过对这个示例程序的分析,我们可以学习到:

  • Binder 应用层涉及的三个进程
  • Binder 应用层工作的流程
  • Binder 与驱动交互的数据结构

# 驱动分析

在熟悉了 C 层以后,我们就可以开始学习 Binder 驱动了。

结合 C 层的示例来分析驱动的实现,主要搞清楚:

  • 服务注册,获取,使用三个情景下,内核中的工作流程
  • 服务注册,获取,使用三个情景下,内核中各种数据结构的变化

通过驱动的分析,我们就能彻底搞懂 Binder 的内部原理了,包括了:

  • 数据传输大小的限制
  • 数据是怎么跨进程传输的
  • 一次拷贝原理
  • 进程/线层的阻塞和唤醒是如何实现的。

# C++ 层分析

C++ 层有一个 libbinder 库,这个库也是对 open ioctl mmap close 这些系统调用的封装,相比 binder.c 的封装更为完善,功能更多,内部实现更为复杂。

首先我们要写一个基于 libbinder 库的 Demo (opens new window)。基于这个 Demo, 我们来分析 libbinder 库中的类和函数,理清楚服务的注册获取使用三大流程。

基于这个 Demo,我们还需要分析 libbinder 中对几个特殊场景的处理:

  • 死亡通知
  • 多线程
  • 匿名服务

# Java 层分析

首先,我们先写一个 Java 层的完整示例。我们要明白 Java 层只是一层马甲,其核心功能都是通过 JNI 调用到 libbinder 库实现的,所以我们需要对 JNI 编程有基本的了解。

接着我们基于这个示例,分析三个情景下的执行过程与各个类与函数的流程与功能:

  • Java 层初始化
  • 服务注册过程分析
  • 服务获取与使用过程分析

当然还有一些其他高级特性也需要我们分析:

  • AIDL 中 in out inout oneway 的分析
  • Parcel 数据结构分析
  • Java 层死亡通知
  • Java 层多线程分析
  • 匿名服务

# 疑难问题

不论是应用开发还是系统开发我们都会遇到一些棘手的 bug,很多时候这些 bug 都和 binder 有关,总结起来,大概可以分为几类:

  • 死锁
  • 线程池满了
  • 代理对象内存泄露
  • 传输数据过大
  • 关键方法内发起 Binder 同步调用导致卡顿

这类 bug 很多都难以复现,很多时候都不了了之了,导致拥有这部分经验的同学很少。后续课程中,我们会逐一进行分析。